跳到主要内容

Java注解学习

注解

作用分类:

  • 编写文档:通过代码里标识的元数据生成文档(生成doc文档)
  • 代码检查:通过代码里标识的元数据对代码进行分析(使用反射)
  • 编译检查:通过代码里标识的元数据让编译器让编译器能够实现基本的编译检查(Override)

jdk中预定义的一些注解

@Override : 检测该注解标注的方法是否是继承自父类(接口)的 @Deprecate :该注解标注的内容表示已过时 @SuppressWarnings :压制警告(一般传参“all”)@SuppressWarnings("all") @FunctionalInterface :标识接口是函数式接口

自定义注解

格式(分两块):

  • 元注解
  • public @interface 注解名

本质

package com.alsritter;

public @interface MyAnoo {
}

在命令行里(javap命令)反编译生成的class文件

注解本质就是一个接口,该接口默认继承java.lang.annotation.Annotation

public interface com.alsritter.MyAnoo extends java.lang.annotation.Annotation {
}

属性

注解的属性就是接口中的抽象方法 当一个方法为抽象方法时,意味着这个方法应该被子类的方法所重写,否则其子类的该方法仍然是abstract的

  • 要求:属性的返回值类型(反正就是不包括自定义类型和void

    • 基本数据类型(不包括包装类)
    • String
    • 枚举
    • 注解
    • 以上类型的数组
  • 定义了属性,在使用时就要属性赋值

  • 但是可以设置默认值,这样的话不赋值就自动用默认值(default关键字)

  • 如果只有一个属性需要赋值,那么可以设置这个属性叫value,这样可以省略写属性名

//定义一个注解
public @interface MyAnno {
int age() default 18;
String value();
}


//使用这个注解
@MyAnno("张三")
public class Student {
}

// 赋值一览
@myAnno2(value = 12,per = Person.p1,anno3 = @MyAnno3,strs={"aa","bb"})

元注解

用于描述注解的注解 @Target:描述注解能够作用的位置 使用:ElementType 枚举 @Retention:描述被保留的阶段(源码,class,RunTime)使用:RetentionPolicy 枚举 @Documented:描述注解是否被抽取到api文档 @Inherited:描述注解是否被子类继承

在程序中解析注解

获取注解中定义的属性值 就是通过反射的方式

  • 如果注解是定义在类上 MyAnno myAnno = studentClass.getAnnotation(MyAnno.class);
  • 如果注解是定义在方法上 MyAnno myAnno = method.getAnnotations(MyAnno.class)

example:通过注解来指定创建一个类并执行其方法

//创建个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
String className();
String methodName();
}

@MyAnno(className = "com.alsritter.Student", methodName = "show")
public class Student {
// 这里懒得再定义一个类来反射生成了,所以直接使用main所在的那个类
public void show(){
System.out.println("Hello Anno...");
}

public static void main(String[] args) {
//1.解析注解
// 获取该类的字节码文件对象
Class<Student> studentClass = Student.class;
// 获取上面那个字节码所带的注解对象
// 这里的 getAnnotation 相当于自动在内存中生成了这个注解的实现类(因为注解本质就是一个接口)
MyAnno myAnno = studentClass.getAnnotation(MyAnno.class);
// 调用注解对象中定义的抽象方法,获取返回值(别弄混了,这里的className和methodName是注解里定义的抽象方法)
String className = myAnno.className();
String methodName = myAnno.methodName();


// 然后这里就与注解无关了,通过拿到的全类名和方法名,生成一个对象并执行
// 1.加载该类进内存
Class cls = null;
try {
cls = Class.forName(className);
// 2.创建对象
Object obj = cls.newInstance();
// 3.获取方法对象
Method method = cls.getMethod(methodName);
// 4.执行方法
method.invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}


// 补上面的 getAnnotation
// 相当于自动在内存中生成了这个注解的实现类(因为注解本质就是一个接口)
public class MyAnnoImpl implement MyAnno{
public String className(){
return "通过注解获取的类名"
}
public String methodName(){
return "通过注解获取的方法名"
}
}

example2:创建一个调试框架

// 创建一个用于表示需要测试的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {}



/**
* 用于测试用的计算机类
**/
public class Calculator {

@Check
public void show(){
//测试空指针异常
String str = null;
System.out.println(str.toString());
}
@Check
public void div(){
//测试除零异常
System.out.println(1/0);
}
}


/**
* 测试框架
* 当主方法执行后,会自动自行被检测的所有方法(加了
* Check注解的方法),判断方法是否有异常,记录到文件
*
* @author alsritter
* @version 1.0
**/
public class TestCheck {

public static void main(String[] args) throws IOException {
//1.创建计算器对象
Calculator c = new Calculator();
//2.获取字节码文件对象
Class<? extends Calculator> aClass = c.getClass();

int number = 0; //记录异常出现的次数
BufferedWriter bw = new BufferedWriter(new FileWriter("./bug.log"));

//3.获取所有方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
//4.判断方法上是否有Check注解
if (method.isAnnotationPresent(Check.class)) {
//5.有,执行
try {
method.invoke(c);
} catch (Exception e) {
//6.捕获异常
// 记录到文件中
number++;
bw.write(method.getName() + "方法出现异常了");
bw.newLine();
// getSimpleName()是获取简短类名
bw.write("异常的名称:"+ e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常的原因:"+ e.getCause().getMessage());
bw.newLine();
bw.write("-------------------------------");
bw.newLine();
}
}
}
bw.write("本次测试出现了:"+number+"个错误!");
bw.flush();
bw.close();
}
}

总结

注解就只是一个标识用的工具,它可以通过定义参数抽象方法的形式给该注解添加参数 可以用:method.isAnnotationPresent(Check.class) 之类的方法检测到对象上是否定义了注解